﻿#if UNITY_EDITOR
using UnityEditor;
#endif
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Text.RegularExpressions;
using UnityEngine.EventSystems;
using Devil;

namespace GameToolkit.UI
{
    [DisallowMultipleComponent]
    public class AsyncDataListView : LayoutGroup, IDataList, ISelectableRectModifier, IComparableCollection,
        IFilterableCollection, ISelectableByIndex, ISelectableByData
    {
        public enum ETweenLayout
        {
            Disable,
            Always,
            OnLoaded,
        }

        internal enum EScanType
        {
            Auto,
            ViewportOnly,
            SelectableAdditive,
        }

        internal enum EPresetFlags
        {
            IsToggle = 1,
            IsSelectable = 2,
            TweenLayout = 4,
            DataSource = 8,
            Flexiable = 16,
            ViewSelector = 32,
            Cachable = 64,
            AsyncValidate = 128,
            AsyncLayout = 256,
        }

        public interface IEntity
        {
            bool IsCache { get; }
            Rect rect { get; }
            object Data { get; }
            GameObject gameObject { get; }
        }

        public interface IAsyncDataValidate
        {
            void ValidateAsyncData();
        }

        /// <summary>
        /// 布局回调
        /// </summary>
        public interface IAsyncLayout
        {
            void OnLayout(AsyncDataListView list, IEntity entity);
        }

        /// <summary>
        /// 视图选择器, 用以匹配数据与视图
        /// </summary>
        public interface IDataViewSelector
        {
            bool IsDataView(GameObject view, object data);
        }

        /// <summary>
        /// 大小可调节, 视图组件实现该方法以运行时动态调整组件大小
        /// </summary>
        public interface IFlexiableSize
        {
            event System.Action OnSizeChanged;
            bool GetPreferSize(bool isPrefabInvoke, object data, ref Vector2 size);
        }

        /// <summary>
        /// 布局动画控制
        /// </summary>
        public interface ILayoutTweener
        {
            /// <summary>
            /// 设置布局信息
            /// </summary>
            /// <param name="direct"></param>
            /// <param name="tweenIndex"></param>
            /// <param name="itemRect"></param>
            void TweenLayoutRect(MoveDirection direct, int tweenIndex, Rect itemRect);
            void StopTweenLayout();
        }

        /// <summary>
        /// 可缓存视图组件
        /// </summary>
        public interface ICacheableView
        {
            /// <summary>
            /// 是否可设置为缓存对象
            /// </summary>
            /// <returns></returns>
            bool CanBeCache();

            /// <summary>
            /// 试图释放对象并缓存起来
            /// </summary>
            void MarkAsCache();
        }

        public enum ExpendAxis
        {
            None = 0,
            Horizontal = 1,
            Vertical = 2,
            Both = 3,
        }

        internal class DataView
        {
            RectTransform mRectTrans;
            protected GameObject mView;
            internal IDataSource mSource;
            internal ICacheableView mCachable;
            internal IFlexiableSize mFlexiable;
            internal ILayoutTweener mTweener;
            internal Selectable mToggle;
            internal IAsyncLayout mBinder;
            Vector2 mSize;
            int mCacheId;
            public Vector2 sizeDelta { get { return mSize; } }
            public bool isVaild { get { return mView != null; } }
            public RectTransform transform { get { return mRectTrans; } }
            public GameObject gameObject { get { return mView; } }
            internal bool isCache { get { return mCacheId != -1; } }
            internal bool toggleOn
            {
                get
                {
                    if (mToggle is Toggle)
                        return ((Toggle)mToggle).isOn;
                    if (mToggle is IToggle)
                        return ((IToggle)mToggle).IsOn;
                    return false;
                }
            }

            internal DataView(GameObject view, EPresetFlags flags)
            {
                mView = view;
                mCacheId = -1;
                if (view == null)
                {
                    mSize = Vector2.one;
                }
                else
                {
                    mRectTrans = view.transform as RectTransform;
                    mSize = mRectTrans == null ? Vector2.one : mRectTrans.rect.size;
                    if ((flags & EPresetFlags.DataSource) != 0)
                        mSource = view.GetComponent<IDataSource>();
                    if ((flags & EPresetFlags.TweenLayout) != 0)
                        mTweener = view.GetComponent<ILayoutTweener>();
                    if ((flags & EPresetFlags.IsToggle) != 0)
                        mToggle = view.GetComponent<Selectable>();
                    if ((flags & EPresetFlags.Flexiable) != 0)
                        mFlexiable = view.GetComponent<IFlexiableSize>();
                    if ((flags & EPresetFlags.AsyncLayout) != 0)
                        mBinder = view.GetComponent<IAsyncLayout>();
                    if ((flags & EPresetFlags.Cachable) != 0)
                        mCachable = view.GetComponent<ICacheableView>();
                }
            }

            public override string ToString()
            {
                if (mView != null)
                    return mView.name;
                return base.ToString();
            }

            internal class Pool
            {
                Transform mParent;
                GameObject mView;
                RectTransform mRectTrans;
                internal EPresetFlags mFlags;
                internal IFlexiableSize mFlexiable;
                internal IDataViewSelector mViewSelector;
                int mId;
                public int Id { get { return mId; } }

                // 缓存数量
                int mCacheNum;
                DataView[] mCaches;
                internal Vector2 sizeDelta;

                internal bool isVaild { get { return mRectTrans != null; } }
                internal GameObject gameObject { get { return mView; } }
                internal Transform transform { get { return mRectTrans; } }
                internal bool isSelectable { get { return (mFlags & EPresetFlags.IsSelectable) != 0; } }
                internal bool isToggle { get { return (mFlags & EPresetFlags.IsToggle) != 0; } }
                internal bool isAsyncValidate { get { return (mFlags & EPresetFlags.AsyncValidate) != 0; } }
                internal bool hasAsyncLayout { get { return (mFlags & EPresetFlags.AsyncLayout) != 0; } }
                public Rect rect { get { return mRectTrans == null ? new Rect(0, 0, 1, 1) : mRectTrans.rect; } }
#if UNITY_EDITOR
                int mGenNum;
                static Regex sCacheNameMatch = new Regex(@" \(Cache\)$");
#endif

                public Pool(int id, Transform parent, GameObject view, int capacity) //: base(view)
                {
                    mId = id;
                    mParent = parent;
                    mView = view;
                    mFlags = 0;
                    if (view != null)
                    {
                        mRectTrans = view.transform as RectTransform;
                        sizeDelta = mRectTrans == null ? Vector2.one : mRectTrans.rect.size;
                        mRectTrans.pivot = Vector2.zero;
                        mRectTrans.anchorMin = new Vector2(0, 1f);
                        mRectTrans.anchorMax = new Vector2(0, 1f);
                        mFlexiable = view.GetComponent<IFlexiableSize>();
                        mViewSelector = view.GetComponent<IDataViewSelector>();
                        if (mFlexiable != null)
                            mFlags |= EPresetFlags.Flexiable;
                        if (mViewSelector != null)
                            mFlags |= EPresetFlags.ViewSelector;


                        var sel = view.GetComponent<Selectable>();
                        if ((sel is IToggle) || (sel is Toggle))
                            mFlags |= EPresetFlags.IsToggle;
                        if (sel != null)
                            mFlags |= EPresetFlags.IsSelectable;

                        if (view.GetComponent<ILayoutTweener>() != null)
                            mFlags |= EPresetFlags.TweenLayout;

                        if (view.GetComponent<IDataSource>() != null)
                            mFlags |= EPresetFlags.DataSource;

                        if (view.GetComponent<ICacheableView>() != null)
                            mFlags |= EPresetFlags.Cachable;

                        if (view.GetComponent<IAsyncDataValidate>() != null)
                            mFlags |= EPresetFlags.AsyncValidate;

                        if (view.GetComponent<IAsyncLayout>() != null)
                            mFlags |= EPresetFlags.AsyncLayout;
                    }
                    mCaches = new DataView[capacity];
                    mCacheNum = 0;
                }

                internal DataView GetView(DataView prefer)
                {
                    if (prefer != null && prefer.mCacheId >= 0)
                    {
                        var last = --mCacheNum;
                        if (last == prefer.mCacheId)
                        {
                            mCaches[last] = null;
                        }
                        else
                        {
                            var lastitem = mCaches[last];
                            lastitem.mCacheId = prefer.mCacheId;
                            mCaches[prefer.mCacheId] = lastitem;
                            mCaches[last] = null;
                        }
                        prefer.mCacheId = -1;
#if UNITY_EDITOR
                        prefer.gameObject.name = sCacheNameMatch.Replace(prefer.gameObject.name, "");
#endif
                        if (!prefer.gameObject.activeSelf)
                            prefer.gameObject.SetActive(true);
                        return prefer;
                    }
                    if (mCacheNum > 0)
                    {
                        var last = mCaches[--mCacheNum];
                        last.mCacheId = -1;
#if UNITY_EDITOR
                        last.gameObject.name = sCacheNameMatch.Replace(last.gameObject.name, "");
#endif
                        if (!last.gameObject.activeSelf)
                            last.gameObject.SetActive(true);
                        return last;
                    }
                    var dt = NewData();
                    return dt;
                }

                internal void Cache(DataView view)
                {
                    if (view.mCacheId == -1)
                    {
                        NativeArrayUtil.EnsureNativeSize(ref mCaches, mCacheNum, mCacheNum + 1);
                        view.mCacheId = mCacheNum++;
                        mCaches[view.mCacheId] = view;
#if UNITY_EDITOR
                        if (!sCacheNameMatch.IsMatch(view.gameObject.name))
                            view.gameObject.name = ParallelUtils.Concat(view.gameObject.name, " (Cache)");
#endif
                    }
                }

                internal void HideCaches(AsyncDataListView view)
                {
                    for (int i = mCacheNum - 1; i >= 0; i--)
                    {
                        var c = mCaches[i];
                        c.transform.localPosition = new Vector3(-10000f, -10000f, 0);
                        if (view.m_SetCachedItemInactive)
                            c.gameObject.SetActive(false);
                    }
                }

                internal void CleanData(DataView data)
                {
                    if (data.gameObject != null)
                        GlobalUtil.Destroy(data.gameObject);
                }

                DataView NewData()
                {
                    if (mView == null)
                        return new DataView(null, mFlags);
                    var go = Instantiate(mView, mParent);
                    var view = new DataView(go, mFlags);
                    var rect = view.transform;
                    rect.anchorMin = new Vector2(0, 1f);
                    rect.anchorMax = rect.anchorMin;
                    rect.pivot = Vector2.zero;
                    go.SetActive(true);
#if UNITY_EDITOR
                    go.name = ParallelUtils.Concat(mView.name, "_", ++mGenNum);
#endif
                    return view;
                }
            }

        }

        class Entity : IEntity
        {
            internal readonly AsyncDataListView listView;
            internal readonly int id;
            internal DataView.Pool pool;
            internal object data;
            internal int layoutId;
            internal int row;
            internal float rowStart;
            internal float rowEnd;
            internal Rect _rect;
            bool tweens;
            internal bool wasTweens;
            internal bool hasView { get; private set; }
            internal DataView view { get; private set; }
            internal bool isFlexiable { get { return pool != null && pool.mFlexiable != null; } }

            //public int RowId { get { return row; } }
            public bool IsCache { get { return !hasView; } }
            public Rect rect { get { return _rect; } }
            public GameObject gameObject { get { return hasView ? view.gameObject : null; } }
            public object Data { get { return data; } }
            internal Entity(AsyncDataListView lstView, int id, RectTransform rect)
            {
                this.listView = lstView;
                this.id = id;
                pool = null;
                data = null;
                row = 0;
                this._rect = rect.rect;
                hasView = true;
                wasTweens = false;
                this.view = new DataView(rect.gameObject, EPresetFlags.TweenLayout | EPresetFlags.AsyncLayout);
            }

            internal Entity(AsyncDataListView lstView, int id, DataView.Pool pool, object data)
            {
                this.listView = lstView;
                this.id = id;
                this.pool = pool;
                this.data = data;
                var size = pool.sizeDelta;
                if (pool.mFlexiable != null && pool.mFlexiable.GetPreferSize(true, data, ref size))
                    _rect = new Rect(Vector2.zero, size);
                else
                    _rect = new Rect(Vector2.zero, pool.sizeDelta);
            }

            internal bool TweenLayout(ETweenLayout tween)
            {
                return tween == ETweenLayout.Always || !wasTweens && tween == ETweenLayout.OnLoaded;
            }

            internal void Display(AsyncDataListView lst, MoveDirection direct, ref int index, ETweenLayout enableTweener, bool forceLayout)
            {
                bool wasCache = hasView;// view != null && view.isVaild;
                bool newView = false;
                if (pool != null && !hasView)
                {
                    var v = pool.GetView(view);
                    newView = v != view && v != null;
                    view = v;
                    if (view != null && view.isVaild)
                    {
                        hasView = true;
                        forceLayout = true;
                        if (view.mFlexiable != null)
                            view.mFlexiable.OnSizeChanged += OnResizeDataView;
                    }
                }
                if (forceLayout && hasView)
                {
                    if (view.mSource != null)
                    {
                        view.mSource.SetData(data);
                    }
                    if (newView && pool.isAsyncValidate)
                        view.gameObject.SendMessage("ValidateAsyncData", SendMessageOptions.DontRequireReceiver);
                    if ((wasCache || direct != MoveDirection.None) && TweenLayout(enableTweener) && view.mTweener != null)
                    {
                        if (!wasCache)
                            index++;
                        tweens = true;
                        wasTweens = true;
                        view.mTweener.TweenLayoutRect(wasCache ? MoveDirection.None : direct, index, this._rect);
                    }
                    else
                    {
                        tweens = false;
                        var trans = view.transform;
                        trans.localScale = Vector3.one;
                        trans.sizeDelta = _rect.size;
                        trans.anchoredPosition = _rect.position;
                    }
                    if (view.mBinder != null)
                        view.mBinder.OnLayout(lst, this);
                }
            }

            internal bool CanBeCache(AsyncDataListView lst, int cacheNum)
            {
                if (pool == null || !hasView)
                    return true;
                if (lst.mToSelect == this)
                    return false;
                var ev = EventSystem.current;
                var sel = ev == null ? null : ev.currentSelectedGameObject;
                if ((pool.mFlags & EPresetFlags.IsSelectable) != 0 && sel == view.gameObject)
                    return false;
                if (cacheNum < 4)
                {
                    if ((pool.mFlags & EPresetFlags.IsToggle) != 0 && view.toggleOn)
                        return false;
                    var cacheable = view.mSource as ICacheableView;
                    return cacheable == null || cacheable.CanBeCache();
                }
                return true;
            }

            internal void Cache(bool forceLayout)
            {
                if (pool != null && hasView)
                {
                    if (view.mCachable != null)
                        view.mCachable.MarkAsCache();
                    if (view.mFlexiable != null)
                        view.mFlexiable.OnSizeChanged -= OnResizeDataView;
                    //if (view.mBinder != null)
                    //    view.mBinder.OnUnbind();
                    hasView = false;
                    if (tweens)
                    {
                        view.mTweener.StopTweenLayout();
                    }
                    if (tweens || forceLayout)
                    {
                        var trans = view.transform;
                        trans.localScale = Vector3.one;
                        trans.sizeDelta = _rect.size;
                        trans.anchoredPosition = _rect.position;
                    }
                    tweens = false;
                    pool.Cache(view);
                }
            }

            internal void Release(bool destroy)
            {
                wasTweens = false;
                if (view != null && view.isVaild)
                {
                    if (hasView || view.isCache)
                    {
                        if (tweens)
                        {
                            tweens = false;
                            view.mTweener.StopTweenLayout();
                            view.transform.localScale = Vector3.one;
                        }
                        view.transform.localPosition = new Vector3(-10000, 10000, 0);
                        if (!destroy && listView != null && listView.m_SetCachedItemInactive)
                            view.gameObject.SetActive(false);
                    }
                    if (view.mCachable != null)
                        view.mCachable.MarkAsCache();
                    var ev = EventSystem.current;
                    if (ev != null && ev.currentSelectedGameObject == view.gameObject)
                        ev.SetSelectedGameObject(null);
                }
                if (pool != null && hasView)
                {
                    //if (view.mBinder != null)
                    //    view.mBinder.OnUnbind();
                    hasView = false;
                    if (view.mFlexiable != null)
                        view.mFlexiable.OnSizeChanged -= OnResizeDataView;
                    if (view.mSource != null)
                        view.mSource.SetData(null);
                    if (pool != null)
                        pool.Cache(view);
                }
                if (destroy)
                {
                    view = null;
                    pool = null;
                    data = null;
                }
                layoutId = -1;
            }

            void OnResizeDataView()
            {
                if (layoutId != -1 && listView != null)
                {
                    listView.SetItemLayoutDirty(layoutId);
                }
            }

        }

        internal class LayoutState
        {
            public int index;
            public int rowIndex;
            public Rect rowRect;
            public Rect allRect;
            public float allOffset;
            public bool complete;
            public bool dirty;

            public readonly bool verticalThenHorizontal;
            public readonly float paddingLeft;
            public readonly float paddingRight;
            public readonly float paddingTop;
            public readonly float paddingBottom;
            public readonly float verticalAlign;
            public readonly float horizontalAlign;
            readonly ExpendAxis expendAxis;
            public float paddingHorizontal { get { return paddingLeft + paddingRight; } }
            public float paddingVertical { get { return paddingTop + paddingBottom; } }

            internal LayoutState(AsyncDataListView view)
            {
                rowIndex = 0;
                index = 0;
                verticalThenHorizontal = view.m_VerticalThenHorizontal;
                paddingLeft = view.m_Padding.left;
                paddingRight = view.m_Padding.right;
                paddingTop = view.m_Padding.top;
                paddingBottom = view.m_Padding.bottom;
                verticalAlign = view.GetVerticalAlignment();
                horizontalAlign = view.GetHorizontalAlignment();
                expendAxis = view.m_ExpendChildren;

                rowRect = new Rect(paddingLeft, -paddingTop, 0, 0);
                allRect = rowRect;
                complete = view.mEntities == null || view.mEntities.Count == 0;
            }

            internal bool ShouldRelayout(LayoutState compareTo)
            {
                return verticalThenHorizontal != compareTo.verticalThenHorizontal
                    || expendAxis != compareTo.expendAxis
                    || !Mathf.Approximately(paddingLeft, compareTo.paddingLeft)
                    || !Mathf.Approximately(paddingRight, compareTo.paddingRight)
                    || !Mathf.Approximately(paddingTop, compareTo.paddingTop)
                    || !Mathf.Approximately(paddingBottom, compareTo.paddingBottom)
                    || !Mathf.Approximately(verticalAlign, compareTo.verticalAlign) 
                    || !Mathf.Approximately(horizontalAlign, compareTo.horizontalAlign);
            }
        }

#pragma warning disable CS0649
        [SerializeField, Tooltip("自上而下，然后自左而右排列")]
        bool m_VerticalThenHorizontal;

        [SerializeField]
        ExpendAxis m_ExpendChildren;

        [SerializeField, Tooltip("限定每行最大数量")]
        int m_MaxRowViews;

        [SerializeField]
        RectTransform m_Viewport;

        [SerializeField]
        ETweenLayout m_LayoutTweener;

        [SerializeField, Tooltip("强制扩充显示范围到下一个 Selectable 目标?")]
        EScanType m_ScanType;

        [SerializeField, Tooltip("每个节点之间的间距")]
        Vector2 m_ItemSpace;

        [SerializeField, Tooltip("视图缓冲区大小")]
        Vector2 m_CacheSize;

        [SerializeField]
        int m_InitCapacity = 32;

        [SerializeField, Range(64, 512), Tooltip("每帧完成布局的最大数量")]
        int m_LayoutPerFrame = 128;

        [SerializeField, Tooltip("始终保持数据顺序")]
        bool m_AlwaysSortData;

        [SerializeField, Tooltip("当没有找到适合的视图时，是否使用默认视图")]
        bool m_FallbackDefaultView;

        [SerializeField]
        GameObject m_DefaultView;

        [SerializeField]
        GameObject[] m_OtherViews;

        [SerializeField, Tooltip("重复利用预设视图对象")]
        bool m_ReusePrefabs;

        [SerializeField]
        bool m_SetCachedItemInactive;

#pragma warning restore

        DataView.Pool[] mViewPresets;

        readonly DrivenTransformProperties mDrirenPresetProperties = DrivenTransformProperties.AnchoredPosition
            | DrivenTransformProperties.AnchoredPositionY
            | DrivenTransformProperties.AnchorMin | DrivenTransformProperties.AnchorMax | DrivenTransformProperties.Pivot;

        //readonly DrivenTransformProperties mDrivenSelfProperties = DrivenTransformProperties.Pivot 
        //    | DrivenTransformProperties.AnchorMin | DrivenTransformProperties.AnchorMax | DrivenTransformProperties.size
        //readonly DrivenTransformProperties mDrivenProperties = DrivenTransformProperties.AnchoredPosition 
        //    | DrivenTransformProperties.AnchoredPositionY | DrivenTransformProperties.SizeDelta
        //    | DrivenTransformProperties.AnchorMin | DrivenTransformProperties.AnchorMax | DrivenTransformProperties.Pivot;


        Vector2 mTotalSize;
        Vector2 mViewportSize;

        bool mLayoutDirty;

        int mViewportStart;
        int mViewportEnd = -1;
        Rect mViewportRect;
        // 应该缓存但未能缓存的数据
        RandomList<Entity> mMarkAsCache;

        LayoutState mLayout;
        Entity mToSelect;
        bool mInitialized;
        bool mHasBindingDatas;
        List<Entity> mEntities;
        // 隐藏的对象
        CascadeList<Entity> mHiddenEntities;
        IComparer mComparer;
        IFilter mFilter;
        bool mResort;
        bool mGetViewSelector = true;
        int mRelayoutIndex;
        MoveDirection mMoveDir;
        int mSelectables;
        int mTweenIndex;
        bool mSelectDefault;
        IDataViewSelector mViewSelector;
        int mIdNum;
        public IDataViewSelector ViewSelector
        {
            get
            {
                return GetViewSelector();
            }
            set
            {
                if (mViewSelector != null)
                {
                    mViewSelector = value;
                    mGetViewSelector = value == null;
                }
            }
        }

        public IDataViewSelector GetViewSelector()
        {
            if (mGetViewSelector)
            {
                mGetViewSelector = false;
                mViewSelector = GetComponent<IDataViewSelector>();
            }
            return mViewSelector;
        }

        public IComparer DataComparer
        {
            get { return mComparer; }
            set
            {
                var v = value == null ? GetComponent<IComparer>() : value;
                if (mComparer != v)
                {
                    mComparer = v;
                    mResort = mHasBindingDatas && v != null && mEntities.Count > 1;
                }
            }
        }

        public void ForceSortImmediate()
        {
            if (mComparer != null)
            {
                mResort = mHasBindingDatas && mEntities.Count > 1;
            }
        }

        public IFilter DataFilter
        {
            get { return mFilter; }
            set
            {
                if (mFilter != value)
                {
                    mFilter = value;
                    ForceFilterImmediate();
                }
            }
        }

        public void Select(int index)
        {
            if (mHasBindingDatas && index < mEntities.Count && index >= 0)
            {
                mToSelect = mEntities[index];
                mSelectDefault = true;
            }
            else if (index == -1)
            {
                mToSelect = null;
                mSelectDefault = false;
            }
        }

        public void Select(object data)
        {
            if (mHasBindingDatas && mEntities != null)
            {
                if (data == null)
                {
                    for (int i = 0; i < mEntities.Count; i++)
                    {
                        var it = mEntities[i];
                        if (it.data == null)
                        {
                            mToSelect = it;
                            mSelectDefault = true;
                            return;
                        }
                    }
                }
                else
                {
                    for (int i = 0; i < mEntities.Count; i++)
                    {
                        var it = mEntities[i];
                        if (data.Equals(it.data))
                        {
                            mToSelect = it;
                            mSelectDefault = true;
                            return;
                        }
                    }
                }
            }
        }

        public void ForceFilterImmediate()
        {
            if (!mHasBindingDatas)
                return;
            int layoutIndex = mEntities.Count;
            var dirty = false;
            if (mFilter != null)
            {
                if (mHiddenEntities == null)
                    mHiddenEntities = new CascadeList<Entity>(16);
                var len = mHiddenEntities.Count;
                for (int i = mEntities.Count - 1; i >= 0; i--)
                {
                    var it = mEntities[i];
                    var pass = mFilter.OnFilter(it.data);
                    if (!pass)
                    {
                        if (it.pool.isSelectable)
                            mSelectables--;
                        dirty = true;
                        layoutIndex = i;
                        it.Release(false);
                        mEntities.RemoveAt(i);
                        mHiddenEntities.Add(it);
                        if (i < mViewportStart)
                            mViewportStart--;
                        if (i <= mViewportEnd)
                            mViewportEnd--;
                    }
                }
                for (int i = len - 1; i >= 0; i--)
                {
                    var it = mHiddenEntities[i];
                    var pass = mFilter.OnFilter(it.data);
                    if (pass)
                    {
                        if (it.pool.isSelectable)
                            mSelectables++;
                        dirty = true;
                        var ins = GetInsertIndex(it.data, it);
                        if (ins == mEntities.Count)
                            mEntities.Add(it);
                        else
                            mEntities.Insert(ins, it);
                        if (ins <= mViewportStart)
                            mViewportStart++;
                        if (ins <= mViewportEnd)
                            mViewportEnd++;
                        if (ins < layoutIndex)
                            layoutIndex = ins;
                        mHiddenEntities.RemoveAt(i);
                    }
                }
            }
            else if (mHiddenEntities != null)
            {
                dirty |= mHiddenEntities.Count > 0;
                for (int i = 0; i < mHiddenEntities.Count; i++)
                {
                    var it = mHiddenEntities[i];
                    if (it.pool.isSelectable)
                        mSelectables++;
                    var ins = GetInsertIndex(it.data, it);
                    if (ins == mEntities.Count)
                        mEntities.Add(it);
                    else
                        mEntities.Insert(ins, it);
                    if (ins < layoutIndex)
                        layoutIndex = ins;
                    if (ins <= mViewportStart)
                        mViewportStart++;
                    if (ins <= mViewportEnd)
                        mViewportEnd++;
                }
                mHiddenEntities.Clear();
            }

            if (dirty)
            {
                SetRelayout(layoutIndex > 0 ? layoutIndex - 1 : 0);
            }
        }

        #region layout

        bool IsExpendChildAxis(ExpendAxis axis)
        {
            return (m_ExpendChildren & axis) == axis;
        }

        void InitItems()
        {
            if (this == null)
                return;
            mInitialized = true;
            mMoveDir = MoveDirection.None;
            if (mComparer == null)
                mComparer = GetComponent<IComparer>();
            if (mMarkAsCache == null)
                mMarkAsCache = new RandomList<Entity>(4, (a, b) => a == b);
            var len = (m_OtherViews == null ? 0 : m_OtherViews.Length) + 1;
            var len2 = mViewPresets == null ? 0 : mViewPresets.Length;
            if (len != len2)
            {
                mViewPresets = new DataView.Pool[len];
            }
            //var cacheOff = m_VerticalThenHorizontal ? new Vector2(0, -10000f) : new Vector2(10000f, 0);
            if (m_DefaultView != null)
                mViewPresets[0] = new DataView.Pool(0, transform, m_DefaultView, m_InitCapacity);
            var cap = Mathf.Min(m_InitCapacity >> 1, 8);
            for (int i = 1; i < len; i++)
            {
                var v = m_OtherViews[i - 1];
                if (v != null)
                    mViewPresets[i] = new DataView.Pool(i, transform, v, cap);
            }
#if UNITY_EDITOR
            if (!EditorApplication.isPlayingOrWillChangePlaymode)
                return;
#endif
            for (int i = 0; i < mViewPresets.Length; i++)
            {
                var view = mViewPresets[i];
                if (view == null)
                    continue;
                if (view.gameObject.activeInHierarchy)
                    view.gameObject.SetActive(false);
                if (m_ReusePrefabs)
                    view.Cache(new DataView(view.gameObject, view.mFlags));
            }
        }

        bool ValidateTotalHeight(float height)
        {
            if (height > mTotalSize.y + 1)
            {
                mTotalSize.y = height;
                return true;
            }
            return false;
        }

        bool ValidateTotalWidth(float width)
        {
            if (width > mTotalSize.x + 1)
            {
                mTotalSize.x = width;
                return true;
            }
            return false;
        }

        // 从 index 位置开始重新布局
        void StartLayoutAt(LayoutState layout, int index)
        {
            if (index < 0 || index >= mEntities.Count)
                return;
            if (layout.index > 0 && index >= layout.index)
            {
                if (layout.complete)
                    index = layout.index - 1;
                else
                    return;
            }
            int rowOffset = 0;
            var row = mEntities[index].row - 1;
            bool find = false;
            while (index > 0)
            {
                index--;
                //var i = rowOffset - 1;
                var it = mEntities[index];
                if (it.layoutId == -1)
                {
                    continue;
                }
                if (it.row == row)
                {
                    find = true;
                    rowOffset = index;
                }
                else if (find)
                {
                    break;
                }
            }
            // 完全重新开始布局
            if (rowOffset == 0)
            {
                layout.index = 0;
                layout.rowIndex = 0;
                layout.rowRect = new Rect(layout.paddingLeft, -layout.paddingTop, 0, 0);
                layout.allOffset = 0;
                layout.allRect = layout.rowRect;
            }
            else
            {
                var allRect = layout.allRect;
                if (Mathf.Abs(layout.allOffset) > 0.1)
                {
                    if (layout.verticalThenHorizontal)
                        allRect.x -= layout.allOffset;
                    else
                        allRect.yMin -= layout.allOffset;
                    for (int i = 0; i < mEntities.Count; i++)
                    {
                        var entity = mEntities[i];
                        var rect = entity._rect;
                        if (layout.verticalThenHorizontal)
                            rect.x -= layout.allOffset;
                        else
                            rect.y -= layout.allOffset;
                    }
                }
                layout.allOffset = 0;
                var it = mEntities[rowOffset]._rect;
                Rect rowRect;
                if (layout.verticalThenHorizontal)
                    rowRect = new Rect(it.x, -layout.paddingTop, 0, 0);
                else
                    rowRect = new Rect(layout.paddingLeft, it.yMax, 0, 0);
                var len = Mathf.Min(layout.index, mEntities.Count);
                for (int i = rowOffset + 1; i < len; i++)
                {
                    var entity = mEntities[i];
                    if (entity.row != row)
                        break;
                    rowRect.x = Mathf.Min(rowRect.x, entity._rect.x);
                    rowRect.y = Mathf.Max(rowRect.y, entity._rect.yMax);
                }
                layout.index = rowOffset;
                layout.rowIndex = row;
                layout.rowRect = rowRect;
                if (layout.verticalThenHorizontal)
                    allRect.xMax = rowRect.x;
                else
                    allRect.yMin = rowRect.y;
                layout.allRect = allRect;
            }
            layout.complete = mEntities == null && mEntities.Count == 0;
            layout.dirty = true;
            if (layout.verticalThenHorizontal)
            {
                var width = Mathf.Max(mViewportSize.x, layout.allRect.xMax + layout.paddingRight);
                mLayoutDirty |= Mathf.Abs(width - mTotalSize.x) > 1;
                mTotalSize.x = width;
            }
            else
            {
                var height = Mathf.Max(mViewportSize.y, -layout.allRect.yMin + layout.paddingBottom);
                mLayoutDirty |= Mathf.Abs(height - mTotalSize.y) > 1;
                mTotalSize.y = height;
            }
        }

        void FinishHorizontalRow(IList<Entity> list, int rowOffset, int end, float rowWidth, LayoutState layout)
        {
            float deltaX = 0;
            var rowRect = layout.rowRect;
            var allRect = layout.allRect;
            if (IsExpendChildAxis(ExpendAxis.Horizontal) && rowRect.width < rowWidth && end - rowOffset > 1)
            {
                deltaX = (rowWidth - rowRect.width) / (end - rowOffset - 1f);
                rowRect.width = rowWidth;
                allRect.xMax = Mathf.Max(rowRect.xMax, allRect.xMax);
            }
            float offsetX = (rowWidth - rowRect.width) * layout.horizontalAlign;
            float rstart = layout.rowRect.yMax;
            float rend = layout.rowRect.yMin;
            for (int i = rowOffset; i < end; i++)
            {
                var entity = list[i];
                entity.row = layout.rowIndex;
                var rect = entity._rect;
                var offsetY = (rect.height - rowRect.height) * layout.verticalAlign;
                rect.position += new Vector2(offsetX + (i - rowOffset) * deltaX, offsetY);
                entity._rect = rect;
                entity.rowStart = rstart;
                entity.rowEnd = rend;
            }
            rowRect.x = layout.paddingLeft;
            rowRect.y = rowRect.yMin - m_ItemSpace.y;
            rowRect.height = 0;
            rowRect.width = 0;
            layout.rowRect = rowRect;
            layout.allRect = allRect;
            layout.rowIndex++;
        }

        void DoHorizontalRowLayout(LayoutState layout)
        {
            int rowOffset = layout.index;
            float xMax = mViewportSize.x - layout.paddingLeft;
            float widthMax = mViewportSize.x - layout.paddingHorizontal;
            while (layout.index < mEntities.Count)
            {
                var entity = mEntities[layout.index];
                entity.wasTweens = false;
                var rect = entity._rect;
                //var flexiable = entity.layoutId == -1 && entity.isFlexiable;
                //if (flexiable)
                //{
                //    var flex = entity.pool.mFlexiable;
                //    rect.size = flex.GetPreferSize(entity.data);
                //}
                entity.layoutId = layout.index;
                if (layout.index > rowOffset)
                {
                    // next row
                    if (IsLimitedCols(layout.index - rowOffset) || layout.rowRect.xMax + rect.width + m_ItemSpace.x > xMax)
                    {
                        FinishHorizontalRow(mEntities, rowOffset, layout.index, widthMax, layout);
                        if (mHasBindingDatas)
                            mLayoutDirty |= ValidateTotalHeight(layout.allRect.height + layout.paddingVertical);
                        return;
                    }
                    else
                    {
                        layout.rowRect.width += m_ItemSpace.x;
                    }
                }
                rect.position = new Vector2(layout.rowRect.xMax, layout.rowRect.yMax - rect.height);
                entity._rect = rect;
                layout.rowRect.width += rect.width;
                layout.rowRect.yMin = Mathf.Min(layout.rowRect.yMin, rect.yMin);
                layout.allRect.xMax = Mathf.Max(layout.allRect.xMax, layout.rowRect.xMax);
                layout.allRect.yMin = Mathf.Min(layout.allRect.yMin, layout.rowRect.yMin);
                layout.index++;
            }
            FinishHorizontalRow(mEntities, rowOffset, mEntities.Count, widthMax, layout);
            layout.rowIndex++;
            if (IsExpendChildAxis(ExpendAxis.Vertical))
            {
                var deltaY = (layout.allRect.height - mViewportSize.y + layout.paddingVertical) * layout.verticalAlign;
                if (deltaY < -1)
                {
                    layout.allOffset = deltaY;
                    layout.allRect.yMin = -mViewportSize.y + layout.paddingBottom;
                    for (int i = 0; i < mEntities.Count; i++)
                    {
                        var entity = mEntities[i];
                        var rect = entity._rect;
                        rect.y += deltaY;
                        entity._rect = rect;
                        entity.rowStart += deltaY;
                        entity.rowEnd += deltaY;
                    }
                }
            }
            var height = layout.allRect.height + layout.paddingVertical;
            //mTotalSize.y = height;
            mLayoutDirty |= ValidateTotalHeight(height);
            layout.complete = true;
        }

        void LayoutHorizontalRows()
        {
            var layout = new LayoutState(this);
            while (!layout.complete)
            {
                DoHorizontalRowLayout(layout);
            }
        }

        void FinishVeritcalRow(IList<Entity> list, int rowOffset, int end, float rowHeight, LayoutState layout)
        {
            var rowRect = layout.rowRect;
            var allRect = layout.allRect;
            float deltaY = 0;
            if (IsExpendChildAxis(ExpendAxis.Vertical) && rowRect.height < rowHeight && end - rowOffset > 1)
            {
                deltaY = (rowRect.height - rowHeight) / (end - rowOffset - 1f);
                rowRect.yMin = rowRect.yMax - rowHeight;
                allRect.yMin = Mathf.Min(rowRect.yMin, allRect.yMin);
            }
            float offsetY = (rowRect.height - rowHeight) * layout.verticalAlign;
            float rstart = rowRect.xMin;
            float rend = rowRect.xMax;
            for (int i = rowOffset; i < end; i++)
            {
                var entity = list[i];
                entity.row = layout.rowIndex;
                var rect = entity._rect;
                var offsetX = (rowRect.width - rect.width) * layout.horizontalAlign;
                rect.position += new Vector2(offsetX, offsetY + (i - rowOffset) * deltaY);
                entity._rect = rect;
                entity.rowStart = rstart;
                entity.rowEnd = rend;
            }
            rowRect.x = rowRect.xMax + m_ItemSpace.x;
            rowRect.y = -layout.paddingTop;
            rowRect.height = 0;
            rowRect.width = 0;
            layout.rowRect = rowRect;
            layout.allRect = allRect;
            layout.rowIndex++;
        }

        void DoVerticalRowLayout(LayoutState layout)
        {
            int rowOffset = layout.index;
            float yMin = -mViewportSize.y + layout.paddingBottom;
            float heightMax = mViewportSize.y - layout.paddingVertical;
            while (layout.index < mEntities.Count)
            {
                var entity = mEntities[layout.index];
                entity.wasTweens = false;
                var rect = entity._rect;
                //var flexiable = entity.layoutId == -1 && entity.isFlexiable;
                //if (flexiable)
                //{
                //    rect.size = entity.pool.mFlexiable.GetPreferSize(entity.data);
                //}
                entity.layoutId = layout.index;
                if (layout.index > rowOffset)
                {
                    // next row
                    if (IsLimitedCols(layout.index - rowOffset) || layout.rowRect.yMin - rect.height - m_ItemSpace.y < yMin)
                    {
                        FinishVeritcalRow(mEntities, rowOffset, layout.index, heightMax, layout);
                        if (mHasBindingDatas)
                            mLayoutDirty |= ValidateTotalWidth(layout.allRect.width + layout.paddingHorizontal);
                        return;
                    }
                    else
                    {
                        layout.rowRect.yMin -= m_ItemSpace.y;
                    }
                }
                rect.position = new Vector2(layout.rowRect.xMin, layout.rowRect.yMin - rect.height);
                entity._rect = rect;
                layout.rowRect.xMax = Mathf.Max(layout.rowRect.xMax, rect.xMax);
                layout.rowRect.yMin -= rect.height;
                layout.allRect.xMax = Mathf.Max(layout.allRect.xMax, layout.rowRect.xMax);
                layout.allRect.yMin = Mathf.Min(layout.allRect.yMin, layout.rowRect.yMin);
                layout.index++;
            }
            FinishVeritcalRow(mEntities, rowOffset, mEntities.Count, heightMax, layout);
            layout.rowIndex++;
            if (IsExpendChildAxis(ExpendAxis.Horizontal))
            {
                var deltaX = (mViewportSize.x - layout.paddingHorizontal - layout.allRect.width) * layout.horizontalAlign;
                if (deltaX > 1)
                {
                    layout.allOffset = deltaX;
                    layout.allRect.xMax = mViewportSize.x - layout.paddingRight;
                    for (int i = 0; i < mEntities.Count; i++)
                    {
                        var entity = mEntities[i];
                        var rect = entity._rect;
                        rect.x += deltaX;
                        entity._rect = rect;
                        entity.rowStart += deltaX;
                        entity.rowEnd += deltaX;
                    }
                }
            }
            var width = layout.allRect.width + layout.paddingHorizontal;
            //mTotalSize.x = width;
            mLayoutDirty |= ValidateTotalWidth(width);
            layout.complete = true;
        }

        void LayoutVerticalRows()
        {
            var layout = new LayoutState(this);
            while (!layout.complete)
            {
                DoVerticalRowLayout(layout);
            }
        }

        protected override void Awake()
        {
            base.Awake();

            //InitTotalSize();
            if (!mInitialized)
                InitItems();
            //mTotalSize = mViewportSize;
        }

        protected override void OnEnable()
        {
            base.OnEnable();
            mMoveDir = MoveDirection.None;
            mRelayoutIndex = -1;
            mViewportRect = GetViewportRect();
            mLayout = new LayoutState(this);
            if (mHasBindingDatas)
            {
                SetRelayout(0);
                //if (!mResort)
                //    StartLayoutAt(mLayout, 0);
            }
        }

        protected override void OnDisable()
        {
            base.OnDisable();
            if (mEntities != null && mHasBindingDatas)
            {
                for (int i = 0; i < mEntities.Count; i++)
                {
                    mEntities[i].Release(false);
                }
            }
        }

        private void Update()
        {
            mViewportRect = GetViewportRect();
            if (mHasBindingDatas)
            {
                if (mResort)
                {
                    mResort = false;
                    ResortDatas();
                    return;
                }
                if (mRelayoutIndex != -1)
                {
                    StartLayoutAt(mLayout, mRelayoutIndex);
                    mRelayoutIndex = -1;
                    mTweenIndex = 0;
                }
                if (!mLayout.complete)
                {
                    var i = mLayout.index;
                    if (mLayout.verticalThenHorizontal)
                    {
                        while (!mLayout.complete && mLayout.index - i < m_LayoutPerFrame)
                        {
                            DoVerticalRowLayout(mLayout);
                        }
                    }
                    else
                    {
                        while (!mLayout.complete && mLayout.index - i < m_LayoutPerFrame)
                        {

                            DoHorizontalRowLayout(mLayout);
                        }
                    }
                }
                else
                {
                    ScanViewport();
                }
                if (mLayoutDirty)
                {
                    mLayoutDirty = false;
                    SetDirty();
                }
            }
        }

        void ResortDatas()
        {
            mMoveDir = MoveDirection.None;
            for (int i = mViewportStart; i <= mViewportEnd; i++)
            {
                var it = mEntities[i];
                if (!mMarkAsCache.Contains(it))
                    mMarkAsCache.Add(it);
            }
            mViewportStart = 0;
            mViewportEnd = -1;
            var ev = EventSystem.current;
            var sel = ev == null ? null : ev.currentSelectedGameObject;
            if (sel != null && sel.transform.IsChildOf(transform))
                ev.SetSelectedGameObject(null);
            for (int i = 0; i < mEntities.Count; i++)
            {
                var it = mEntities[i];
                int ins = i;
                for (int k = i - 1; k >= 0; k--)
                {
                    if (mComparer.Compare(mEntities[k].data, mEntities[i].data) <= 0)
                    {
                        break;
                    }
                    ins = k;
                }
                if (ins < i)
                {
                    for (int k = i; k > ins; k--)
                    {
                        mEntities[k] = mEntities[k - 1];
                        mEntities[k].wasTweens = false;
                    }
                    mEntities[ins] = it;
                    it.wasTweens = false;
                    SetRelayout(ins);
                }
            }
        }

        // 扫描可视区域
        protected virtual void ScanViewport()
        {
            var cacheviewport = GetCacheRect(mViewportRect);
            float start, end;
            if (mLayout.verticalThenHorizontal)
            {
                start = mViewportRect.xMin;
                end = mViewportRect.xMax;
            }
            else
            {
                start = mViewportRect.yMax;
                end = mViewportRect.yMin;
            }
            mTweenIndex = 0;
            int starti, endi;
            FindNearestEntityRange(mLayout, start, end, out starti, out endi);

            // clear mark as cache data.
            for (int i = mMarkAsCache.Count - 1; i >= 0; i--)
            {
                var it = mMarkAsCache[i];
                if (it.layoutId < starti || it.layoutId > endi)
                {
                    if (it.CanBeCache(this, mMarkAsCache.Count - 1))
                    {
                        it.Cache(mLayout.dirty);
                        mMarkAsCache.RemoveAt(i);
                        continue;
                    }
                    //else if (mLayout.dirty)
                    //{
                    //    it.Display(mMoveDir, ref tweenId, m_LayoutTweener, true);
                    //}
                }
                else
                {
                    mMarkAsCache.RemoveAt(i);
                }
                if (mLayout.dirty)
                    it.Display(this, mMoveDir, ref mTweenIndex, m_LayoutTweener, true);
            }
            //clear prev. items
            if (starti > mViewportEnd + 1 || endi < mViewportStart - 1)
            {
                for (int i = mViewportStart; i <= mViewportEnd; i++)
                {
                    var it = mEntities[i];
                    if (it.layoutId == -1)
                        continue;
                    if (it.CanBeCache(this, mMarkAsCache.Count))
                    {
                        it.Cache(mLayout.dirty);
                    }
                    else if (!mMarkAsCache.Contains(it))
                    {
                        mMarkAsCache.Add(it);
                    }
                }
            }
            else
            {
                for (int i = mViewportStart; i < starti; i++)
                {
                    var it = mEntities[i];
                    if (it.layoutId == -1)
                        continue;
                    if (cacheviewport.Overlaps(it._rect))
                    {
                        starti = i;
                        break;
                    }
                    if (it.CanBeCache(this, mMarkAsCache.Count))
                    {
                        it.Cache(mLayout.dirty);
                    }
                    else if (!mMarkAsCache.Contains(it))
                    {
                        mMarkAsCache.Add(it);
                    }
                }
                for (int i = mViewportEnd; i > endi; i--)
                {
                    var it = mEntities[i];
                    if (it.layoutId == -1)
                        continue;
                    if (cacheviewport.Overlaps(it._rect))
                    {
                        endi = i;
                        break;
                    }
                    if (it.CanBeCache(this, mMarkAsCache.Count))
                    {
                        it.Cache(mLayout.dirty);
                    }
                    else if (!mMarkAsCache.Contains(it))
                    {
                        mMarkAsCache.Add(it);
                    }
                }
            }

            if (mViewportEnd < endi)
                mMoveDir = mLayout.verticalThenHorizontal ? MoveDirection.Right : MoveDirection.Down;
            else if (mViewportStart > starti)
                mMoveDir = mLayout.verticalThenHorizontal ? MoveDirection.Left : MoveDirection.Up;

            var dirty = mLayout.dirty || mViewportStart != starti || mViewportEnd != endi;
            mViewportStart = starti;
            mViewportEnd = endi;

            if (mToSelect != null)
            {
                mToSelect.Display(this, mMoveDir, ref mTweenIndex, m_LayoutTweener, mLayout.dirty);
                if ((mToSelect.layoutId < mViewportStart || mToSelect.layoutId > mViewportEnd) && !mMarkAsCache.Contains(mToSelect))
                    mMarkAsCache.Add(mToSelect);
            }
            if (dirty)
            {
                mTweenIndex = 0;
                for (int i = starti; i <= endi; i++)
                {
                    var it = mEntities[i];
                    if (it.layoutId == -1)
                        continue;
                    it.Display(this, mMoveDir, ref mTweenIndex, m_LayoutTweener, mLayout.dirty);
                }
            }
            if (mLayout.dirty)
            {
                for (int i = 0; i < mViewPresets.Length; i++)
                {
                    var view = mViewPresets[i];
                    if (view != null)
                        view.HideCaches(this);
                }
                mLayout.dirty = false;
            }
            mTweenIndex = 0;
            if (mSelectDefault)
            {
                mSelectDefault = false;
                RectTransform trans = null;
                if (mToSelect != null && mToSelect.hasView && mToSelect.pool.isSelectable)
                {
                    trans = mToSelect.view.transform;
                }
                else
                {
                    for (int i = mViewportStart; i <= mViewportEnd; i++)
                    {
                        var it = mEntities[i];
                        if (!it.hasView || !it.pool.isSelectable)
                            continue;
                        trans = it.view.transform;
                        break;
                    }
                }
                if (trans != null)
                {
                    var nav = GetComponentInParent<SelectableNavigation>();
                    if (nav != null)
                        nav.SelectDefault(trans);
                }
            }
            mToSelect = null;
        }

        float GetHorizontalAlignment()
        {
            switch (m_ChildAlignment)
            {
                case TextAnchor.UpperCenter:
                case TextAnchor.MiddleCenter:
                case TextAnchor.LowerCenter:
                    return 0.5f;
                case TextAnchor.UpperRight:
                case TextAnchor.MiddleRight:
                case TextAnchor.LowerRight:
                    return 1f;
                default:
                    return 0f;
            }
        }

        float GetVerticalAlignment()
        {
            switch (m_ChildAlignment)
            {
                case TextAnchor.LowerLeft:
                case TextAnchor.LowerCenter:
                case TextAnchor.LowerRight:
                    return 1f;
                case TextAnchor.MiddleLeft:
                case TextAnchor.MiddleCenter:
                case TextAnchor.MiddleRight:
                    return 0.5f;
                default:
                    return 0f;
            }
        }

        Rect GetViewportRect()
        {
            if (m_Viewport == null)
            {
                return new Rect(0, -mTotalSize.y, mTotalSize.x, mTotalSize.y);
            }
            else
            {
                var matrix = transform.worldToLocalMatrix * m_Viewport.localToWorldMatrix;
                var rect = m_Viewport.rect;
                var min = matrix.MultiplyPoint(rect.min);
                var max = matrix.MultiplyPoint(rect.max);
                return new Rect(min.x, min.y, max.x - min.x, max.y - min.y);
            }
        }

        Rect GetCacheRect(Rect rect)
        {
            return new Rect(rect.x - m_CacheSize.x * 0.5f, rect.y - m_CacheSize.y * 0.5f,
                rect.width + m_CacheSize.x, rect.height + m_CacheSize.y);
        }

        // 寻找 pos 附近的数据 (include startIndex & endIndex)
        void FindNearestEntityRange(LayoutState layout, float start, float end, out int startIndex, out int endIndex)
        {
            var l = 0;
            var size = Mathf.Min(DataCount, layout.index);
            var r = size - 1;
            int c;
            startIndex = l;
            endIndex = r;
            int row = -1;
            float sign = layout.verticalThenHorizontal ? 1 : -1;
            var expend = m_ScanType == EScanType.SelectableAdditive || m_ScanType == EScanType.Auto && mSelectables > 0;
            // find start
            while (l <= r)
            {
                c = (l + r) >> 1;
                var entity = mEntities[c];
                var dis = (entity.rowEnd - start) * sign;
                if (dis > 0)
                {
                    row = entity.row;
                    startIndex = c;
                    r = c - 1;
                }
                else
                {
                    //row = entity.row;
                    //startIndex = c;
                    l = c + 1;
                }
            }
            for (int i = startIndex - 1; i >= 0; i--)
            {
                var entity = mEntities[i];
                if (entity.row == row)
                    startIndex = i;
                else
                    break;
            }
            if (expend)
            {
                bool gotIt = false;
                for (int i = startIndex - 1; i >= 0; i--)
                {
                    var it = mEntities[i];
                    if (it.pool.isSelectable)
                    {
                        if (!gotIt)
                        {
                            gotIt = true;
                            row = it.row;
                            startIndex = i;
                        }
                        else if (row == it.row)
                        {
                            startIndex = i;
                        }
                        else
                        {
                            break;
                        }
                    }
                    else if (gotIt)
                    {
                        break;
                    }
                }
            }
            l = 0;
            r = size - 1;
            row = -1;
            // find end
            while (l <= r)
            {
                c = (l + r) >> 1;
                var entity = mEntities[c];
                var dis = (mEntities[c].rowStart - end) * sign;
                if (dis < 0)
                {
                    row = entity.row;
                    endIndex = c;
                    l = c + 1;
                }
                else
                {
                    //row = entity.row;
                    //endIndex = c;
                    r = c - 1;
                }
            }
            for (int i = endIndex + 1; i < size; i++)
            {
                var entity = mEntities[i];
                if (entity.row == row)
                    endIndex = i;
                else
                    break;
            }
            if (expend)
            {
                bool gotIt = false;
                for (int i = endIndex + 1; i < size; i++)
                {
                    var it = mEntities[i];
                    if (it.pool.isSelectable)
                    {
                        if (!gotIt)
                        {
                            gotIt = true;
                            row = it.row;
                            endIndex = i;
                        }
                        else if (row == it.row)
                        {
                            endIndex = i;
                        }
                        else
                        {
                            break;
                        }
                    }
                    else if (gotIt)
                    {
                        break;
                    }
                }
            }
        }

        public override void CalculateLayoutInputHorizontal()
        {
            //InitTotalSize();
            if (m_Viewport != null)
            {
                var size = m_Viewport.rect.size;
                var dirty = MathExt.IsDifferent(mViewportSize, size, 1);
                mViewportSize = size;
                if (dirty)
                {
                    if (m_VerticalThenHorizontal)
                    {
                        mTotalSize.y = size.y;
                        ValidateTotalWidth(size.x);
                    }
                    else
                    {
                        mTotalSize.x = size.x;
                        ValidateTotalHeight(size.y);
                    }
                    SetRelayout(0);
                }
            }
            base.CalculateLayoutInputHorizontal();
            if (!mHasBindingDatas)
            {
                mTweenIndex = 0;
                mTotalSize = mViewportSize;
                var children = rectChildren;
                if (mEntities == null)
                {
                    mEntities = new List<Entity>(Mathf.Max(5, children.Count));
                }
                else
                {
                    mEntities.Clear();
                }
                mIdNum = 0;
                for (int i = 0; i < children.Count; i++)
                {
                    var rect = children[i];
                    var entity = new Entity(this, mIdNum++, rect);
                    mEntities.Add(entity);
                }
                if (m_VerticalThenHorizontal)
                {
                    LayoutVerticalRows();
                }
                else
                {
                    LayoutHorizontalRows();
                }
            }

            SetLayoutInputForAxis(mViewportSize.x, mTotalSize.x, 0, 0);
            SetLayoutInputForAxis(mViewportSize.y, mTotalSize.y, 0, 1);
        }

        public override void CalculateLayoutInputVertical()
        {
            //throw new System.NotImplementedException();
        }

        bool IsLimitedCols(int cols)
        {
            return m_MaxRowViews > 0 && m_MaxRowViews <= cols;
        }

        public override void SetLayoutHorizontal()
        {
            var t = rectTransform;
            var drivenProperties = DrivenTransformProperties.Pivot;
            m_Tracker.Add(this, t, drivenProperties);
            t.pivot = new Vector2(0, 1f);
            t.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, mTotalSize.x);
            t.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, mTotalSize.y);
            var rpivot = new Vector2(0, 0);
            if (!mHasBindingDatas && mEntities != null)
            {
                MainThread.RunOnMainThread(ApplyRelayout, EExecuteEvent.LateUpdate);
            }
            //else
            //{
            //     Track Item Properties
            //}
        }

        public override void SetLayoutVertical()
        {
            rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, mTotalSize.y);
        }

        void ApplyRelayout()
        {
            if (!mHasBindingDatas && mEntities != null && this != null)
            {
                var t = rectTransform;
                var rpivot = new Vector2(0, 0);
                for (int i = 0; i < mEntities.Count; i++)
                {
                    var entity = mEntities[i];
                    if (entity.view.isVaild)
                    {
                        var rect = entity.view.transform;
                        m_Tracker.Add(this, rect, mDrirenPresetProperties);
                        rect.anchorMin = t.pivot;
                        rect.anchorMax = t.pivot;
                        rect.pivot = rpivot;
#if UNITY_EDITOR
                        var tween = Application.isPlaying && entity.TweenLayout(m_LayoutTweener);
#else
                        var tween = entity.TweenLayout(m_LayoutTweener);
#endif
                        if (tween && entity.view.mTweener != null)
                        {
                            entity.view.mTweener.TweenLayoutRect(MoveDirection.None, i, entity.rect);
                            entity.wasTweens = true;
                        }
                        else
                            rect.anchoredPosition = entity._rect.position;
                        if (entity.view.mBinder != null)
                            entity.view.mBinder.OnLayout(this, entity);
                    }
                }
            }
        }

        #endregion


        #region Gizmos
#if UNITY_EDITOR

        [ContextMenu("Relayout")]
        void RelayoutAll()
        {
            mLayout = new LayoutState(this);
            SetRelayout(0);
        }

        protected override void OnValidate()
        {
            if (m_Viewport == null)
            {
                AutoGetViewport();
            }
            if (!Application.isPlaying)
            {
                if (m_Viewport != null)
                    mViewportSize = m_Viewport.rect.size;
                MainThread.RunOnMainThread(InitItems);
            }
            else if(mLayout != null)
            {
                var layout = new LayoutState(this);
                if(layout.ShouldRelayout(mLayout))
                {
                    mLayout = layout;
                    SetRelayout(0);
                }
            }
            base.OnValidate();
        }

        [ContextMenu("Get Viewport")]
        private void AutoGetViewport()
        {
            var scroll = GetComponentInParent<ScrollRect>();
            if (scroll == null)
                return;
            var dirty = scroll.viewport != m_Viewport;
            m_Viewport = scroll.viewport;
            if (dirty)
                EditorUtility.SetDirty(this);
        }

        private void OnDrawGizmosSelected()
        {
            if (m_Viewport != null)
            {
                var rect = GetViewportRect();
                rect = GetCacheRect(rect);
                Gizmos.matrix = transform.localToWorldMatrix;
                Gizmos.color = Color.green;
                DrawRectGizmos(rect);

                if (mHasBindingDatas && mEntities != null)
                {
                    for (int i = 0; i < mEntities.Count; i++)
                    {
                        Gizmos.color = mEntities[i].hasView ? Color.yellow : Color.gray;
                        DrawRectGizmos(mEntities[i]._rect);
                    }
                }
                else
                {
                    //var rect = m_Viewport.rect;
                    //rect.size += m_CacheSize;
                    //rect.position -= m_CacheSize * 0.5f;
                    //Gizmos.matrix = m_Viewport.localToWorldMatrix;
                    //Gizmos.color = Color.green;
                    //DrawRectGizmos(rect);
                    var len = mViewPresets == null ? 0 : mViewPresets.Length;
                    for (int i = 0; i < len; i++)
                    {
                        var item = mViewPresets[i];
                        Gizmos.color = i == 0 ? Color.yellow : Color.black;
                        if (item != null && item.isVaild)
                        {
                            rect = item.rect;
                            rect.position = (transform.worldToLocalMatrix * item.transform.localToWorldMatrix).MultiplyPoint(rect.position);
                            DrawRectGizmos(rect);
                        }
                    }
                }
            }
        }

        void DrawRectGizmos(Rect rect)
        {
            Gizmos.DrawRay(rect.min, Vector3.right * rect.width);
            Gizmos.DrawRay(rect.min, Vector3.up * rect.height);
            Gizmos.DrawRay(rect.max, -Vector3.right * rect.width);
            Gizmos.DrawRay(rect.max, -Vector3.up * rect.height);

        }
#endif
        #endregion

        #region IDataList implemetation
        public int DataCount { get { return mEntities == null ? 0 : mEntities.Count; } }

        void SetRelayout(int index)
        {
            //if (index > 0)
            //    index = index - 1;
            if (mRelayoutIndex == -1 || mRelayoutIndex > index)
                mRelayoutIndex = index;
        }

        bool FilterAndHideEntity(Entity entity)
        {
            if (mFilter == null || mFilter.OnFilter(entity.data))
                return false;
            if (mHiddenEntities == null)
                mHiddenEntities = new CascadeList<Entity>(16);
            mHiddenEntities.Add(entity);
            return true;
        }

        //bool ISelectableHandler.SelectDefault()
        //{
        //    mSelectDefault = true;
        //    return true;
        //}

        SelectableRect ISelectableRectModifier.GetSelectableRect(GameObject sel, Transform asParent)
        {
            if (sel == gameObject)
            {
                return new SelectableRect(SelectableNavigation.GetLocalRect(asParent, transform, mViewportRect));
            }
            for (int i = mViewportStart; i <= mViewportEnd; i++)
            {
                var it = mEntities[i];
                if (it.hasView && it.view.gameObject == sel)
                {
                    return new SelectableRect(SelectableNavigation.GetLocalRect(asParent, transform, it._rect));
                }
            }
            for (int i = 0; i < mMarkAsCache.Count; i++)
            {
                var it = mMarkAsCache[i];
                if (it.hasView && it.view.gameObject == sel)
                {
                    return new SelectableRect(SelectableNavigation.GetLocalRect(asParent, transform, it._rect));
                }
            }
            return default;
        }

        public GameObject GetDataViewModel(object data)
        {
            var pool = GetPool(data);
            return pool == null ? null : pool.gameObject;
        }

        DataView.Pool GetPool(object data)
        {
            if (!mInitialized)
                InitItems();
            if (mViewPresets == null || mViewPresets.Length == 0)
                return null;
            var sel = GetViewSelector();
            //DataView.Pool defPool = null;
            for (int i = 0; i < mViewPresets.Length; i++)
            {
                var pool = mViewPresets[i];
                if (pool == null)
                    continue;
                var exclude = sel != null && !sel.IsDataView(pool.gameObject, data);
                if (exclude)
                    continue;
                exclude = pool.mViewSelector != null && !pool.mViewSelector.IsDataView(pool.gameObject, data);
                if (!exclude && (sel != null || pool.mViewSelector != null))
                    return pool;

            }
            //if (m_FallbackDefaultView && defPool == null)
            //    defPool = mViewPresets[0];
            return m_FallbackDefaultView ? mViewPresets[0] : null;// defPool;
        }

        public void ClearData()
        {
            if (mEntities != null)
            {
                mSelectables = 0;
                mMarkAsCache.Clear();
                for (int i = 0; i < mEntities.Count; i++)
                {
                    mEntities[i].Release(true);
                }
                mEntities.Clear();
                mViewportStart = 0;
                mViewportEnd = -1;
                mResort = false;
                mRelayoutIndex = -1;
                var ev = EventSystem.current;
                var sel = ev == null ? null : ev.currentSelectedGameObject;
                if (sel != null && sel.transform.IsChildOf(transform))
                    ev.SetSelectedGameObject(null);

                if (MathExt.IsDifferent(mTotalSize, mViewportSize, 1f))
                {
                    mTotalSize = mViewportSize;
                    mLayoutDirty = true;
                }
            }
            if (mHiddenEntities != null)
            {
                for (int i = 0; i < mHiddenEntities.Count; i++)
                {
                    mHiddenEntities[i].Release(true);
                }
                mHiddenEntities.Clear();
            }
            //mHasBindingDatas = false;
        }

        int GetInsertIndex(object data, Entity entity)
        {
            var ins = mEntities.Count;
            if (mComparer != null)
            {
                for (int i = ins - 1; i >= 0; i--)
                {
                    var it = mEntities[i];
                    if (mComparer.Compare(it.data, data) <= 0)
                        break;
                    ins = i;
                }
            }
            else if (m_AlwaysSortData && entity != null)
            {
                for (int i = ins - 1; i >= 0; i--)
                {
                    var it = mEntities[i];
                    if (it.id <= entity.id)
                        break;
                    ins = i;
                }
            }
            return ins;
        }

        // 以视图预制体作为数据绑定
        public void BindViews<T>() where T : class
        {
            var lst = CacheUtil.GetListCahce<T>();
            var t1 = m_DefaultView == null ? null : m_DefaultView.GetComponent<T>();
            if (t1 != null)
                lst.Add(t1);
            for (int i = 0; i < m_OtherViews.Length; i++)
            {
                var v = m_OtherViews[i];
                t1 = v == null ? null : v.GetComponent<T>();
                if (t1 != null)
                    lst.Add(t1);
            }
            BindData(lst);
        }

        public void BindData(IEnumerable iter)
        {
            ((IDataList)this).ClearData();
            mHasBindingDatas = true;
            if (mEntities == null)
                mEntities = new List<Entity>(m_InitCapacity);
            else
                mEntities.Clear();
            mIdNum = 0;
            var it = iter.GetEnumerator();
            while (it != null && it.MoveNext())
            {
                var data = it.Current;
                var pool = GetPool(data);
                if (pool == null)
                {
                    Debug.LogErrorFormat(this, "无法指定数据 \"{0}\" 的视图组件", data);
                    continue;
                }
                var entity = new Entity(this, mIdNum++, pool, data);
                //entity.data = data;
                entity.layoutId = -1;
                if (FilterAndHideEntity(entity))
                    continue;
                if (pool.isSelectable)
                    mSelectables++;
                //entity.wasTweens = true;
                int insert = GetInsertIndex(data, null);
                if (insert == mEntities.Count)
                    mEntities.Add(entity);
                else
                    mEntities.Insert(insert, entity);
            }
            // execute layout
            mRelayoutIndex = -1;
            if (mLayout != null)
                StartLayoutAt(mLayout, 0);
        }

        public void AddData(object data)
        {
            var pool = GetPool(data);
            if (pool == null)
                throw new System.Exception(string.Format("无法指定数据 \"{0}\" 的视图组件", data));
            if (!mHasBindingDatas)
            {
                if (mEntities == null)
                    mEntities = new List<Entity>();
                else
                    mEntities.Clear();
                mHasBindingDatas = true;
            }
            var entity = new Entity(this, mIdNum++, pool, data);
            //entity.data = data;
            entity.layoutId = -1;
            if (FilterAndHideEntity(entity))
                return;
            if (pool.isSelectable)
                mSelectables++;
            if (mResort || mComparer == null)
            {
                SetRelayout(mEntities.Count);
                mEntities.Add(entity);
            }
            else
            {
                var ins = GetInsertIndex(data, null);
                if (ins == mEntities.Count)
                {
                    mEntities.Add(entity);
                }
                else
                {
                    var it = mEntities[ins];
                    entity.rowStart = it.rowStart;
                    entity.rowEnd = it.rowEnd;
                    mEntities.Insert(ins, entity);
                    if (ins <= mViewportEnd)
                        mViewportEnd++;
                    if (ins <= mViewportStart)
                        mViewportStart++;
                }
                SetRelayout(ins);
            }
        }

        public void InsertData(int index, object data)
        {
            if (mComparer != null || m_AlwaysSortData)
                throw new System.Exception(string.Format("数据集合已经实现了排序方法，插入\"{0}\"操作无法完成", data));
            if (index < 0 || index > mEntities.Count)
                throw new System.IndexOutOfRangeException();
            var pool = GetPool(data);
            if (pool == null)
                throw new System.Exception(string.Format("无法指定数据 \"{0}\" 的视图组件", data));
            if (!mHasBindingDatas)
            {
                if (mEntities == null)
                    mEntities = new List<Entity>();
                else
                    mEntities.Clear();
                mHasBindingDatas = true;
            }
            var entity = new Entity(this, mIdNum++, pool, data);
            //entity.data = data;
            entity.layoutId = -1;
            if (FilterAndHideEntity(entity))
                return;
            if (index == mEntities.Count)
            {
                mEntities.Add(entity);
            }
            else
            {
                var it = mEntities[index];
                entity.rowStart = it.rowStart;
                entity.rowEnd = it.rowEnd;
                mEntities.Insert(index, entity);
                if (index <= mViewportEnd)
                    mViewportEnd++;
                if (index <= mViewportStart)
                    mViewportStart++;
            }
            SetRelayout(index > 0 ? (index - 1) : index);
        }

        public void UpdateData(object data)
        {
            if (mEntities == null)
                return;
            for (int i = 0; i < mEntities.Count; i++)
            {
                var it = mEntities[i];
                if (it.data == data)
                {
                    var src = it.hasView ? it.view.mSource : null;
                    if (src != null)
                        src.ApplyUpdate();
                    return;
                }
            }
        }

        public void UpdateDataAt(int index)
        {
            if (index >= 0 && mEntities != null && index < mEntities.Count)
            {
                var item = mEntities[index];
                var src = item.hasView ? item.view.mSource : null;
                if (src != null)
                    src.ApplyUpdate();
            }
        }

        public virtual void SetData(int index, object data)
        {
            if (mComparer != null)
                throw new System.Exception(string.Format("数据集合已经实现了自定义排序方法，插入\"{0}\"操作无法完成", data));
            if (index < 0 || index >= mEntities.Count)
                throw new System.IndexOutOfRangeException();
            var pool = GetPool(data);
            if (pool == null)
                throw new System.Exception(string.Format("无法指定数据 \"{0}\" 的视图组件", data));
            var entity = mEntities[index];
            if (entity.pool != pool)
            {

                var hide = mFilter != null && !mFilter.OnFilter(data);
                bool relayout;
                if (hide)
                {
                    relayout = true;
                }
                else
                {
                    var size = entity._rect.size;
                    if (pool.mFlexiable != null && pool.mFlexiable.GetPreferSize(true, data, ref size))
                        relayout = MathExt.IsDifferent(entity._rect.size, size, 1f);
                    else
                        relayout = false;
                    if (relayout)
                    {
                        var rect = entity._rect;
                        rect.size = size;
                        entity._rect = rect;
                    }
                }
                var layoutid = entity.layoutId;
                var hasView = entity.hasView && !relayout;
                entity.Release(true);
                entity.pool = pool;
                entity.data = data;
                if (hide)
                {
                    mEntities.RemoveAt(index);
                    if (index <= mViewportEnd)
                        mViewportEnd--;
                    if (index < mViewportStart)
                        mViewportStart--;
                    if (mHiddenEntities == null)
                        mHiddenEntities = new CascadeList<Entity>(16);
                    mHiddenEntities.Add(entity);
                }
                if (relayout)
                {
                    SetRelayout(index > 0 ? (index - 1) : 0);
                }
                else if (!hide)
                {
                    entity.layoutId = layoutid;
                    if (hasView)
                        entity.Display(this, MoveDirection.None, ref mTweenIndex, m_LayoutTweener, true);
                }
            }
            else if (entity.data != data)
            {
                entity.data = data;
                var hide = FilterAndHideEntity(entity);// mFilter != null && !mFilter.Filter(data);
                if (hide)
                {
                    entity.Release(false);
                    mEntities.RemoveAt(index);
                    if (index <= mViewportEnd)
                        mViewportEnd--;
                    if (index < mViewportStart)
                        mViewportStart--;
                    SetRelayout(index > 0 ? (index - 1) : 0);
                }
                else
                {
                    //if (entity.hasView && entity.view.mSource != null)
                    //    entity.view.mSource.SetData(data);
                    bool relayout;
                    var size = entity._rect.size;
                    if (pool.mFlexiable != null && pool.mFlexiable.GetPreferSize(true, data, ref size))
                        relayout = MathExt.IsDifferent(entity._rect.size, size, 1);
                    else
                        relayout = false;
                    //var size = pool.mFlexiable == null ? pool.sizeDelta : pool.mFlexiable.GetPreferSize(data);
                    //var relayout = MathExt.IsDifferent(entity._rect.size, size, 1);
                    if (relayout)
                    {
                        entity.Release(false);
                        var rect = entity._rect;
                        rect.size = size;
                        entity._rect = rect;
                        SetRelayout(index > 0 ? (index - 1) : 0);
                    }
                    else if (entity.hasView && entity.view.mSource != null)
                    {
                        entity.view.mSource.SetData(data);
                    }
                }
            }
        }

        public GameObject ForceDisplay(int index)
        {
            if (!mHasBindingDatas || index < 0 || index >= mEntities.Count)
                throw new System.IndexOutOfRangeException();
            var it = mEntities[index];
            if (!it.hasView)
            {
                it.Display(this, MoveDirection.None, ref mTweenIndex, m_LayoutTweener, true);
                if (!mMarkAsCache.Contains(it))
                    mMarkAsCache.Add(it);
            }
            return it.hasView ? it.view.gameObject : null;
        }


        public void SetItemLayoutDirty(int index)
        {
            var entity = mEntities[index];
            var flexiable = entity.hasView ? entity.view.mFlexiable : (entity.pool != null ? entity.pool.mFlexiable : null);
            if (flexiable != null)
            {
                var size = entity._rect.size;
                if (!flexiable.GetPreferSize(!entity.hasView, entity.data, ref size))
                    size = entity._rect.size;
                //var size = flexiable.GetPreferSize(entity.data);
                if (MathExt.IsDifferent(size, entity._rect.size, 1))
                {
                    entity._rect.size = size;
                    if (!mResort)
                        SetRelayout(index);
                }
            }
        }

        public void RemoveData(object data)
        {
            if (mHasBindingDatas)
            {
                for (int i = 0; i < mEntities.Count; i++)
                {
                    var it = mEntities[i];
                    if (it.data == data)
                    {
                        ((IDataList)this).RemoveAt(i);
                        return;
                    }
                }
                if (mHiddenEntities != null)
                {
                    for (int i = 0; i < mHiddenEntities.Count; i++)
                    {
                        var it = mHiddenEntities[i];
                        if (it.data == data)
                        {
                            it.Release(true);
                            mHiddenEntities.RemoveAt(i);
                            return;
                        }
                    }
                }
            }
        }

        public void RemoveAt(int index)
        {
            var it = mEntities[index];
            mEntities.RemoveAt(index);
            it.Release(true);
            if (index <= mViewportEnd)
                mViewportEnd--;
            if (index < mViewportStart)
                mViewportStart--;
            SetRelayout(index > 0 ? (index - 1) : 0);
        }

        public object GetData(int index)
        {
            return mEntities[index].data;
        }

        public IEntity GetEntity(int index)
        {
            return mEntities[index];
        }

        public int GetLayoutIndex(IEntity entity)
        {
            var t = entity as Entity;
            if (t == null)
                return -1;
            return t.layoutId;
        }

        public T GetDataComponent<T>(int index) where T : class
        {
            var inst = GetDataInstance(index);
            T com;
            if (inst != null && inst.TryGetComponent(out com))
                return com;
            else
                return null;
        }

        public IDataSource GetDataSource(int index)
        {
            var entity = mEntities[index];
            return !entity.hasView || entity.view == null || !entity.view.isVaild ? null : entity.view.mSource;
        }

        public GameObject GetDataInstance(int index)
        {
            var entity = mEntities[index];
            return !entity.hasView || entity.view == null || !entity.view.isVaild ? null : entity.view.gameObject;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new WrapEnumerator<Entity, object>(mEntities.GetEnumerator(), (t) => t.data);
        }

        public GameObject FindDataInstance(object data)
        {
            for (int i = 0; i < mMarkAsCache.Count; i++)
            {
                var it = mMarkAsCache[i];
                if (it.data == data)
                {
                    return it.hasView ? it.view.gameObject : null;
                }
            }
            for (int i = mViewportStart; i <= mViewportEnd; i++)
            {
                var it = mEntities[i];
                if (it.data == data)
                {
                    return it.hasView ? it.view.gameObject : null;
                }
            }
            return null;
        }

        public int GetInstanceIndex(GameObject instance)
        {
            for (int i = mViewportStart; i <= mViewportEnd; i++)
            {
                var it = mEntities[i];
                if (it.hasView && it.view.gameObject == instance)
                    return i;
            }
            for (int i = 0; i < mMarkAsCache.Count; i++)
            {
                var it = mMarkAsCache[i];
                if (it.hasView && it.view.gameObject == instance)
                {
                    return it.layoutId;
                }
            }
            return -1;
        }

        public int GetInstanceIndex(FilterDelegate<IEntity> filter)
        {
            for (int i = mViewportStart; i <= mViewportEnd; i++)
            {
                var it = mEntities[i];
                if (filter(it))
                {
                    return i;
                }
            }
            for (int i = 0; i < mMarkAsCache.Count; i++)
            {
                var it = mMarkAsCache[i];
                if (filter(it))
                {
                    return it.layoutId;
                }
            }
            return -1;
        }

        #endregion


    }
}